#!/usr/local/bin/python3

import os
import datetime
import textwrap
import urllib.request
import time
import shutil
import sys
import re
import textwrap
from os import path
from datetime import datetime, timedelta
from xml.dom.minidom import parse, parseString
import sys, select
from cache import cache
from MtaParser import MtaParser
from colors import fg, bg
import inspect
from utils import log, settings
from string import Template
import http.client
from urllib.parse import urlparse, parse_qs

zips = ("11230", "10038")
zidx = 0
fullAlerts = False
fullParking = False

forecast_url = "http://www.weather.gov/forecasts/xml/SOAP_server/ndfdXMLclient.php?whichClient=NDFDgen&zipCodeList=%s&product=time-series&maxt=maxt&mint=mint&temp=temp&wspd=wspd&wdir=wdir&wx=wx&rh=rh&snow=snow&wwa=wwa&sky=sky&appt=appt&Submit=Submit"
current_obs_url = "http://www.weather.gov/xml/current_obs/%s.xml"
alternate_parking_url = "http://www.nyc.gov/html/dot/downloads/excel/altside2010.csv"
mta_alerts_url = "http://www.mta.info/alert/alertnyct.htm"
geocode_url = 'http://geocoder.us/service/csv/geocode?zip=%s'

def get(el, name):
	return el.getElementsByTagName(name)[0].firstChild.nodeValue

	
def printCurrentConditions(station_id):
	s = cache.retrieve(current_obs_url % station_id, "%s.xml" % station_id)	
	dom = parseString(s)
	cu = dom.getElementsByTagName("current_observation")[0]
	timestr = get(cu, "observation_time_rfc822")
	tm = time.strptime(timestr[:-6], "%a, %d %b %Y %H:%M:%S")
	station = get(cu, "station_id")
	temp = get(cu, "temp_c")
	humy = get(cu, "relative_humidity")
	pressure = float(get(cu, "pressure_mb")) * 0.75006
	weather = get(cu, "weather")
	wind_mph = float(get(cu, "wind_mph"))
	wind_dir = get(cu, "wind_dir")
	
	if wind_dir == "North": wind_dir = "N "
	if wind_dir == "South": wind_dir = "S "
	if wind_dir == "West": wind_dir = "W"
	if wind_dir == "East": wind_dir = "E "
	if wind_dir == "Northeast": wind_dir = "NE"
	if wind_dir == "Southeast": wind_dir = "SE"
	if wind_dir == "Southwest": wind_dir = "SW"
	if wind_dir == "Northwest": wind_dir = "NW"
	if wind_dir == "Variable": wind_dir = "Var"

	print("%s %s: %sC %s%% %.0fmm %.0fmph %s; %s" % (time.strftime("%H:%M", tm), station, temp, humy, pressure, wind_mph, wind_dir, weather))

	
class TimeLayout:
	key = ''
	times = []
	interval = 1

	
class Seria:
	name = ''
	timelayout = 0
	values = []

def td(s, style = '', fgcolor = ''):
	if len(fgcolor) > 0:
		s = '<font color="' + fgcolor + '">' + s + '</color>'

	if len(style) > 0:
		return '<td class="' + style + '">' + s + '</td>\n'
	return '<td>' + s + '</td>\n'

def tr(s, style = ''):
	if len(style) > 0:
		return '<tr class="' + style + '">\n' + s + '</tr>\n'
	return '<tr>\n' + s + '</tr>\n'


class WdmlParser():
	winddir = ['N',       'NE',      'E',    'SE', 'S', 'SW', 'W', 'NW']
	windarr = ['&#8593;', '&#8599;', '&#8594;', '&#8600;', '&#8595;', '&#8601;', '&#8592;', '&#8598;']
	HOURS = 24*7
	
	def dump(o):
		for property, value in inspect.getmembers(o):
#			print(property, ": ", len(value))
			print(property)


	def getTimeLayouts(dom):
		timelayouts = {}
		starttime = datetime.max
		data = dom.firstChild.getElementsByTagName("data")[0]
		tlayouts = data.getElementsByTagName("time-layout")
		for tlayout in tlayouts:
			timelayout = TimeLayout()
			timelayout.key = tlayout.getElementsByTagName("layout-key")[0].firstChild.nodeValue
			timelayout.times = []
			m = re.search('k-p([0-9]+)h-', timelayout.key)
			timelayout.interval = int(m.group(1))

			times = tlayout.getElementsByTagName("start-valid-time")
			for t in times:
				timestr = t.firstChild.nodeValue[:19]
				tm = datetime.strptime(timestr, '%Y-%m-%dT%H:00:00')
				timelayout.times.append(tm)
				if tm < starttime:
					starttime = tm
			timelayouts[timelayout.key] = timelayout
		return timelayouts, starttime


	def getIntencity(intencity):
		if intencity == 'light':
			return 'LT'
		if intencity == 'very light':
			return 'lt'
		if intencity == 'moderate':
			return 'MO'
		if intencity == 'heavy':
			return 'HV'
		return '??'

	def parseWeatherConditions(node, timelayout):
		series = []
		seriaWeatherType = Seria()
		seriaWeatherType.name = 'Weather Type'
		seriaWeatherType.timelayout = timelayout
		seriaWeatherType.values = []
		
		seriaVisibility = Seria()
		seriaVisibility.name = 'Visibility'
		seriaVisibility.timelayout = timelayout
		seriaVisibility.values = []
		
		seriaSnowIntensity = Seria()
		seriaSnowIntensity.name = 'Snow Intensity'
		seriaSnowIntensity.timelayout = timelayout
		seriaSnowIntensity.values = []
		
		seriaRainIntensity = Seria()
		seriaRainIntensity.name = 'Rain Intensity'
		seriaRainIntensity.timelayout = timelayout
		seriaRainIntensity.values = []
		
		conditions = node.getElementsByTagName('weather-conditions')
		for cond in conditions:
			values = cond.getElementsByTagName('value')
			visibility = ''
			snowint = ''
			rainint = ''
			for v in values:
				wt = v.getAttribute('weather-type')
				intencity = v.getAttribute('intensity')
				
				if wt == 'snow' or wt == 'snow showers':
					snowint = WdmlParser.getIntencity(intencity)
					
				if wt == 'rain' or wt == 'rain showers':
					rainint = WdmlParser.getIntencity(intencity)
					
				vnodes = v.getElementsByTagName('visibility')
				if len(vnodes) > 0:
					if vnodes[0].hasChildNodes():
						visibility = vnodes[0].firstChild.nodeValue
						
			seriaSnowIntensity.values.append(snowint)
			seriaRainIntensity.values.append(rainint)
			seriaWeatherType.values.append(' ')
			seriaVisibility.values.append(visibility)
		
		series.append(seriaSnowIntensity)
		series.append(seriaRainIntensity)
#			series.append(seriaWeatherType)
		series.append(seriaVisibility)
		return series

	def parseValues(node, timelayout):
		seria = Seria()
		name = node.getElementsByTagName("name")[0]
		seria.name = name.firstChild.nodeValue
		seria.timelayout = timelayout
		seria.values = []
		
		values = node.getElementsByTagName('value')
		for v in values:
			nv = ''
			if v.hasChildNodes():
				nv = v.firstChild.nodeValue

			if seria.name.find('Temperature') >= 0:
				nv = "%3d" % ((int(nv) - 32) * 5 / 9)
			if seria.name == 'Relative Humidity':
				nv = int(nv)
				if nv < 0:
					nv = ''
			if seria.name == 'Wind Direction':
				nv = WdmlParser.windarr[int(((360-int(nv)+22.5) % 360) / 45)]
			if seria.name == 'Snow Amount':
				if nv == '0':
					nv = ''
			seria.values.append(nv)
		return seria

	def download(url, fname):
		try:
			cache.download(url, fname, timeout = 30)
		except Exception as details:
			log.error(details)
			if path.isfile(fname):
				os.remove(fname)
			raise


	def parseData(dom):
		timelayouts, starttime = WdmlParser.getTimeLayouts(dom)
			
		series = []
		parameters = dom.firstChild.getElementsByTagName("parameters")[0]
		for node in parameters.childNodes:
			if node.nodeType != dom.ELEMENT_NODE:
				continue
				
			name = node.getElementsByTagName("name")[0]
			timelayout = timelayouts[node.getAttribute('time-layout')]

			if name.firstChild.nodeValue == 'Weather Type, Coverage, and Intensity':
				series += WdmlParser.parseWeatherConditions(node, timelayout)
			else:
				seria = WdmlParser.parseValues(node, timelayout)
				if seria.name.find('Daily') == 0:
					continue
				series.append(seria)
		return series, starttime
		
	def parseHead(dom):
		head = dom.getElementsByTagName('head')[0]
		product = head.getElementsByTagName('product')[0]
		creationdate = product.getElementsByTagName('creation-date')[0]
		refreshfrequency = creationdate.getAttribute('refresh-frequency')
		return creationdate.firstChild.nodeValue, refreshfrequency


	def createTable(series, timestart):
		
		table = [['' for row in range(0, WdmlParser.HOURS + 1)] for i in range(0, len(series) + 2)]

		table[0][0] = 'Filled'
		table[1][0] = 'Day'
		table[2][0] = 'Hour'
		
		for i in range(2, WdmlParser.HOURS + 1):
			tm = timestart + timedelta(hours = i - 2)
			hour = ' ' + tm.strftime('%H')
			day = ' ' + tm.strftime('%a')[:2]
			
			table[0][i] = 0
			table[1][i] = day
			table[2][i] = hour
			
		rx = 3
		for s in series:
			if s.name == 'Watches, Warnings, and Advisories':
				continue
			
			table[rx][0] = s.name
			table[rx][1] = 0 # empty flag
			for cx in range(0, len(s.timelayout.times)):
				delta = s.timelayout.times[cx] - timestart
				d = delta.days * 24 + int(delta.seconds / 3600) + 1
				if d > 0 and d < WdmlParser.HOURS:
					if type(s.values[cx]) == int or len(s.values[cx]) > 0:
						table[rx][d] = s.values[cx]
						table[0][d] = 1
						table[rx][1] = 1
			rx += 1

		return table
		
	def printTable(table):
		for i in range(1, WdmlParser.HOURS):
			if table[0][i] == 1:
				print(fg(str(table[1][i]).rjust(3), 'red'), end = '')
		print('')
		
		for i in range(2, len(table)):
			row = table[i]
			if row[0] == 'Filled':
				continue
			print(row[0].ljust(40), end = '')
			for i in range(1, WdmlParser.HOURS):
				if table[0][i] == 1:
					print(str(row[i]).rjust(3), end = '')
			print('')
	
	def rowToHtml(table, rowNum, refRowNum, tdstyle = 'td'):
		
		if table[rowNum][1] == 0:
			return ''
		
		s = ''
		prevday = ''
		idx = 1
		
		style = ''
		if refRowNum != 0:
			style = tdstyle + '0'
		
		row = table[rowNum]
		refrow = table[refRowNum]
		t = td(row[0], 'left')
		for cx in range(2, WdmlParser.HOURS + 1):
			if table[0][cx] == 1:
				if refRowNum != 0 and prevday != str(refrow[cx]):
					idx += 1
					style = tdstyle + str(idx % 2)
				fgcolor = ''
				if row[0] == 'Temperature':
					if len(row[cx]) > 0 and  int(row[cx]) < 0:
						fgcolor = 'blue'
						
				t += td(str(row[cx]), style, fgcolor)
				prevday = str(refrow[cx])
				
		return tr(t)
	
	def tableToHtml(table):

		s = '<table>\n'
		s += WdmlParser.rowToHtml(table, 1, 1, 'th')
		s += WdmlParser.rowToHtml(table, 2, 1, 'th')
			
		for rx in range(3, len(table)):
			s += WdmlParser.rowToHtml(table, rx, 1, 'td')
		
		s += '</table>\n'

		return s
	
	def isError(dom):
		errors = dom.getElementsByTagName("error")
		if len(errors) > 0:
			print(errors[0].nodeName)
			return True
		return False
	
	def getAddress(zip):
		godir = settings['tmpdir'] + '/geocode/'
		if not os.path.exists(godir):
			os.mkdir(godir)
			
		url = geocode_url % zip
		fname = godir + '%s.xml' % zip
		
		if cache.download(url, fname, timeout = 365*24*60):
			log.info(url + ' downloaded to ' + fname)
			
		f = open(fname, 'rt')
		s = f.read()
		f.close()
		
		return s
	
	def getForecastHtml(zip):
		url = forecast_url % zip
		fname = settings['tmpdir'] + 'fcast%s.xml' % zip
		
		WdmlParser.download(url, fname + '.tmp')
		log.info(url + ' downloaded to ' + fname + '.tmp')
		dom = parse(fname + '.tmp')
		
		if WdmlParser.isError(dom):
			os.remove(fname + '.tmp')
			log.error(dom.toprettyxml())
			return ('<div>Forecast for zip code ' + zip + ' is not available</div>', False)
		
		shutil.copy(fname + '.tmp', fname)
		log.info(fname + '.tmp copied to ' + fname)
		creationdate, refreshfrequency = WdmlParser.parseHead(dom)
		series, starttime = WdmlParser.parseData(dom)

		address = WdmlParser.getAddress(zip)
		
		table = WdmlParser.createTable(series, starttime)
		
		html = '<div>' + address + '</div>\n'
		html += WdmlParser.tableToHtml(table) + '<br>'
		log.info(zip + ' html generated')
		return (html, True)
	
	def processZips(req):
		
		zips = req.split('/')[3];
		body = ''
		for zip in zips.split(','):
			(text, hr) = WdmlParser.getForecastHtml(zip)
			if hr == False:
				return None
			body += text;

		f = open(settings['bindir'] + '/page.html')
		t = Template(f.read())
		f.close()
		d = dict(body = body, title = zips)

		return t.safe_substitute(d)


def upload(host, path, text):
	print('uploading to ' + host + path)
	headers = {'Content-type': 'text/html'}
	conn = http.client.HTTPConnection(host)
	conn.request('POST', path, text, headers)
	resp = conn.getresponse()
	print(resp.status, resp.reason)
	conn.close()

def respond(req, status, message):
	req.send_response(status)
	req.send_header("Content-type", "text/html")
	req.end_headers()
	req.wfile.write(str.encode(message))


def processRequest(req):

	r = urlparse(req.path)
	q = parse_qs(r.query)
	if not 'lb' in q:
		req.send_response(404)
		req.end_headers()
		return
	host = q['lb'][0]
	debug = 0
	if 'debug' in q:
		debug = q['debug'][0]

	if r.path.find('/files/') == 0:
		with open('.' + r.path) as f:
			s = f.read()
		return s
		
	if r.path.find('/d') == 0:
		return processDefault(r.path)

	if r.path.find('/wx/zip/') == 0:
		text = WdmlParser.processZips(r.path)
		zips = r.path[8:]
		if text == None:
			text = 'Cannot get weather for zip codes: ' + zips
			respond(req, 500, text)
			return
			
		if debug == '1':
			respond(req, 200, text)
			return
			
		upload(host, '/update/' + zips + '.html', text)
		resp = 'http://' + host + '/zip/' + zips;
		req.send_response(302)
		req.send_header("Location", resp)
		req.end_headers()
		return
		
	return respond(req, 200, "USAGE: /wx/zip/zip1,zip2,..,zipN")


def processDefault(req):
	html, hr = WdmlParser.getForecastHtml('10038')
	html += '<pre>' + printAltsideParking() + '</pre>'
	return html


def printAltsideParking():
	fname = 'altside.txt'

	cache.download(alternate_parking_url, fname, 720)
	f = open(fname, 'rt', encoding = 'cp1250')
	s = f.read()
	f.close()
	s = MtaParser.parseAltsideParking(s, True)
	return s


def printMtaAlerts():
	cache.retrieve(mta_alerts_url, "mtaalerts.txt")
	s = MtaParser.parseAlerts(s, fullAlerts)
	print(s)


def printRound():
	for i in range(1, 100):
		print("\n")

	print(time.strftime('%a, %d %b %Y %H:%M', time.localtime()) +  " " + fg('Z', 'cyan') + "ip:" + zips[zidx])
	WdmlParser.printForecast(zidx)
#	print
#	printCurrentConditions("KJFK")
#	printCurrentConditions("KLGA")
#	printCurrentConditions("KNYC")
#	print
#	printAltsideParking()
#	printMtaAlerts()

def main():
	processRequest('/wx/zip/11235')

if __name__ == "__main__":
	main()
